package com.stars.easyms.gateway.filter;

import com.stars.easyms.base.constant.CommonConstants;
import com.stars.easyms.base.constant.HttpHeaderConstants;
import com.stars.easyms.base.encrypt.EasyMsEncrypt;
import com.stars.easyms.base.http.EasyMsRequestSynchronizationManager;
import com.stars.easyms.base.util.*;
import com.stars.easyms.gateway.constant.EasyMsGatewayConstants;
import com.stars.easyms.gateway.properties.EasyMsGatewayProperties;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.entity.ContentType;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.cloud.gateway.filter.factory.rewrite.ModifyRequestBodyGatewayFilterFactory;
import org.springframework.cloud.gateway.filter.factory.rewrite.ModifyRequestBodyGatewayFilterFactory.Config;
import org.springframework.core.Ordered;
import org.springframework.http.HttpHeaders;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.lang.NonNull;
import org.springframework.util.StringUtils;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import javax.annotation.PostConstruct;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;

/**
 * <p>className: EasyMsRequestFilter</p>
 * <p>description: </p>
 *
 * @author guoguifang
 * @version 1.6.1
 * @date 2020/8/27 9:50 上午
 */
@Slf4j
public class EasyMsRequestFilter implements GlobalFilter, Ordered {

    @Autowired
    private EasyMsGatewayProperties gatewayProperties;

    private final Set<String> encryptPermitUrlSet = new HashSet<>();

    private final Set<String> logPermitUrlSet = new HashSet<>();

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {

        // 获取请求与请求头
        ServerHttpRequest request = exchange.getRequest();
        HttpHeaders headers = request.getHeaders();

        // 当前请求路径
        String currentRequestPath = request.getPath().value();

        // 解析请求头，若相应请求头不存在则存放入请求头中
        ServerHttpRequest.Builder requestBuilder = request.mutate();

        // 全链路跟踪ID
        String traceId = headers.getFirst(HttpHeaderConstants.TRACE_KEY);
        if (!StringUtils.hasText(traceId)) {
            traceId = SerialNumberUtil.getSerialNumber();
            requestBuilder.header(HttpHeaderConstants.TRACE_KEY, traceId);
        }

        // 请求系统，如果为空，在gateway中默认请求系统为前端
        String requestSys = headers.getFirst(HttpHeaderConstants.HEADER_KEY_REQUEST_SYS);
        if (!StringUtils.hasText(requestSys)) {
            requestSys = HttpHeaderConstants.FRONT_SYS;
            requestBuilder.header(HttpHeaderConstants.HEADER_KEY_REQUEST_SYS, requestSys);
        }
        String finalRequestSys = requestSys;

        // 获取上游请求ID及下游请求ID
        String upstreamRequestId = headers.getFirst(HttpHeaderConstants.HEADER_KEY_REQUEST_ID);
        if (!StringUtils.hasText(upstreamRequestId)) {
            upstreamRequestId = SerialNumberUtil.getSerialNumber();
        }
        String downstreamRequestId = SerialNumberUtil.getSerialNumber();
        requestBuilder.header(HttpHeaderConstants.HEADER_KEY_REQUEST_ID, downstreamRequestId);

        // 由于响应使用的异步，无法通过本地线程变量传递，因此使用exchange传递
        exchange.getAttributes().put(HttpHeaderConstants.TRACE_KEY, traceId);
        exchange.getAttributes().put(HttpHeaderConstants.HEADER_KEY_REQUEST_SYS, requestSys);
        exchange.getAttributes().put(HttpHeaderConstants.HEADER_KEY_REQUEST_PATH, currentRequestPath);
        exchange.getAttributes().put(HttpHeaderConstants.HEADER_KEY_UPSTREAM_REQUEST_ID, upstreamRequestId);
        exchange.getAttributes().put(HttpHeaderConstants.HEADER_KEY_REQUEST_ID, downstreamRequestId);

        // 请求时间
        String requestTime = headers.getFirst(HttpHeaderConstants.HEADER_KEY_REQUEST_TIME);
        if (!StringUtils.hasText(requestTime)) {
            requestTime = DateTimeUtil.now();
            requestBuilder.header(HttpHeaderConstants.HEADER_KEY_REQUEST_TIME, requestTime);
        }
        String finalRequestTime = requestTime;

        // 如果有header头的设置需要重新设置ServerWebExchange
        exchange = exchange.mutate().request(requestBuilder.build()).build();

        // 将traceId和requestId放入日志线程本地变量中
        EasyMsRequestSynchronizationManager.setTraceInfo(traceId, upstreamRequestId);

        // 如果是上传下载则直接通过，不做加解密
        String contentType = headers.getFirst("Content-Type");
        if (StringUtils.hasText(contentType) && contentType.contains(ContentType.MULTIPART_FORM_DATA.getMimeType())) {
            // 记录接收服务日志信息
            log.info("[路由-接收请求]-[请求地址:{}]-[请求系统:{}]-[请求ID:{}]-[请求时间:{}]",
                    currentRequestPath, finalRequestSys, downstreamRequestId, finalRequestTime);
            return chain.filter(exchange);
        } else if (gatewayProperties.isDebugEnabled()) {
            // 如果是easy-ms相关url则直接跳过
            if (EasyMsUtil.isEasyMsUrl(currentRequestPath)) {
                exchange.getAttributes().put(EasyMsGatewayConstants.DEBUG, true);
                return chain.filter(exchange);
            }
            // 如果是swagger传入的参数不做加解密操作
            String swaggerDebug = headers.getFirst(EasyMsGatewayConstants.SWAGGER_DEBUG_REQUEST_HEADER_KEY);
            if (EasyMsGatewayConstants.SWAGGER_DEBUG_REQUEST_HEADER_VALUE.equals(swaggerDebug)) {
                exchange.getAttributes().put(EasyMsGatewayConstants.SWAGGER_DEBUG, true);
            }
        }

        // 获取requestBody修改工厂类
        return new ModifyRequestBodyGatewayFilterFactory()
                .apply(new Config().setRewriteFunction(String.class, String.class,
                        ((serverWebExchange, requestBody) -> {

                            // 判断是否需要解密
                            boolean isEncrypt = gatewayProperties.getEncrypt().isEnabled();
                            boolean isPermitUrl = isEncryptPermitUrl(currentRequestPath);
                            String decodedRequestBody = null;
                            try {
                                requestBody = requestBody == null ? "{}" : formatRequestBody(requestBody);

                                decodedRequestBody = !JsonUtil.checkAndReturnIsBlankJson(requestBody)
                                        && isEncrypt && !isPermitUrl
                                        && serverWebExchange.getAttribute(EasyMsGatewayConstants.SWAGGER_DEBUG) == null ?
                                        formatRequestBody(EasyMsEncrypt.decode(
                                                requestBody,
                                                gatewayProperties.getEncrypt().getSecret(),
                                                gatewayProperties.getEncrypt().getIv(),
                                                gatewayProperties.getEncrypt().getRequestKey())) : requestBody;
                                return Mono.just(decodedRequestBody);
                            } finally {

                                // 记录接收服务日志信息，此处请求数据可能为加密数据
                                String logData = decodedRequestBody != null ? decodedRequestBody : requestBody;
                                log.info("[路由-接收请求]-[请求地址:{}]-[请求系统:{}]-[请求ID:{}]-[请求时间:{}]-{}请求数据:{}",
                                        currentRequestPath, finalRequestSys, downstreamRequestId, finalRequestTime,
                                        isEncrypt && !isPermitUrl && decodedRequestBody == null ? "加密" : "",
                                        isLogPermitUrl(currentRequestPath) ? "***" : logData);
                            }
                        })))
                .filter(exchange, chain);
    }

    @Override
    public int getOrder() {
        return Ordered.HIGHEST_PRECEDENCE + 1001;
    }

    private String formatRequestBody(@NonNull String requestBody) {
        return requestBody
                .trim()
                .replace("\n", "")
                .replace("\r", "")
                .replace("\t", "");
    }

    @PostConstruct
    private void init() {
        // 获取不需要做加解密的url的集合
        if (StringUtils.hasText(gatewayProperties.getEncrypt().getRequestPermitUrl())) {
            Collections.addAll(encryptPermitUrlSet,
                    StringUtils.tokenizeToStringArray(gatewayProperties.getEncrypt().getRequestPermitUrl(), CommonConstants.PROPERTIES_SEPARATOR));
        }
        if (StringUtils.hasText(gatewayProperties.getLog().getRequestPermitUrl())) {
            Collections.addAll(logPermitUrlSet,
                    StringUtils.tokenizeToStringArray(gatewayProperties.getLog().getRequestPermitUrl(), CommonConstants.PROPERTIES_SEPARATOR));
        }
    }

    private boolean isEncryptPermitUrl(String requestPath) {
        for (String encryptPermitUrl : encryptPermitUrlSet) {
            if (PatternMatcherUtil.doUrlPatternMatch(encryptPermitUrl, requestPath, true)) {
                return true;
            }
        }
        return false;
    }

    private boolean isLogPermitUrl(String requestPath) {
        for (String logPermitUrl : logPermitUrlSet) {
            if (PatternMatcherUtil.doUrlPatternMatch(logPermitUrl, requestPath, true)) {
                return true;
            }
        }
        return false;
    }
}
